package com.niiwoo.civet.trade.service.local.invest;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.niiwoo.civet.trade.constant.TradeRedisKey;
import com.niiwoo.civet.trade.dao.entity.ProjectInvesting;
import com.niiwoo.civet.trade.dao.entity.SysInvestConfig;
import com.niiwoo.civet.trade.enums.PeriodUnitEnum;
import com.niiwoo.civet.trade.enums.RestAreaReasonEnum;
import com.niiwoo.civet.trade.utils.Calculator;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * Created by zhangwanli on 2018/4/25.
 */
@Service
@Slf4j
public class InvestQueueService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public String getQueueName(SysInvestConfig config) {
        PeriodUnitEnum unitEnum = PeriodUnitEnum.enumOf(config.getPeriodUnit());
        Integer minDeadline = config.getMinDeadline();
        Integer maxDeadline = config.getMaxDeadline();
        return getQueueName(unitEnum, minDeadline, maxDeadline);
    }

    private String getQueueName(PeriodUnitEnum unitEnum, Integer minDeadline, Integer maxDeadline) {
        StringBuilder builder = new StringBuilder(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX);
        switch (unitEnum) {
            case MONTH:
                builder.append("m:");
                break;
            case DAY:
                builder.append("d:");
                break;
            default:
                builder.append("x:");
        }
        builder.append(minDeadline);
        builder.append("-");
        builder.append(maxDeadline);
        return builder.toString();
    }

    public String getQueueName(ProjectInvesting project) {
        Byte unit = project.getDeadlineUnit();
        PeriodUnitEnum unitEnum = PeriodUnitEnum.enumOf(unit);
        return getQueueName(unitEnum, project.getDeadline(), project.getDeadline());
    }

    public void enqueueInvestor(String userId, Byte weight, List<SysInvestConfig> configList) {
        double priority = Calculator.investPriority(weight);
        configList.forEach(cfg -> {
            String queueName = getQueueName(cfg);
            doEnqueueInvestor(userId, queueName, priority);
        });
    }

    private void doEnqueueInvestor(String userId, String queueName, double priority) {
        BoundZSetOperations<String, String> zSetOps = redisTemplate.boundZSetOps(queueName);
        Long rank = zSetOps.rank(userId);
        if (rank == null) { //not exists in zset
            zSetOps.add(userId, priority);
        }
    }

    public void updatePriority(String userId, Byte weight, List<SysInvestConfig> list) {
        list.forEach(cfg -> {
            String queueName = getQueueName(cfg);
            Double score = redisTemplate.opsForZSet().score(queueName, userId);
            if (score != null) {
                long longValue = score.longValue();
                double v = score - longValue + weight.longValue();
                dequeueInvestor(userId, queueName);
                doEnqueueInvestor(userId, queueName, v);
            }
        });
    }

    public void dequeueInvestor(String investorId, String queueName) {
        redisTemplate.opsForZSet().remove(queueName, investorId);
        log.info("{}从自动投资人队列{}中移除", investorId, queueName);
    }
    /**
     * 将人员放入对应休息区
     * 
     * @param investorId
     * @param type {@link com.niiwoo.civet.trade.enums.RestAreaReasonEnum}
     */
    public void enqueueInvestorLounge(String investorId, RestAreaReasonEnum type) {
    	long time = System.currentTimeMillis();
    	String redisKey = "";
    	switch (type) {
		case NOT_SUFFICIENT_FUNDS:
			redisKey=TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS;
			break;
		case NO_RED_ENVELOPES :
			redisKey = TradeRedisKey.REST_AREA_NO_RED_ENVELOPES;
			break;
		case RISK_FUND:
			redisKey = TradeRedisKey.REST_AREA_RISK_FUND;
			break;
		case UNKNOW :
			redisKey = TradeRedisKey.REST_AREA_UNKNOW;
			break;
		default:
			redisKey=TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS;
			break;
		}
        redisTemplate.opsForZSet().add(redisKey,investorId ,time);
        log.info("投资人进入休息区,userId={},queue={}", investorId, redisKey);
    }

    public void enqueueInvestorStash(String investorId, String zsetKey, Double priority, Byte weight) {
        AutoInvestorValueDTO value = new AutoInvestorValueDTO();
        value.setInvestorId(investorId);
        value.setSourceQueue(zsetKey);
        long longValue = priority.longValue();
        double score = priority - longValue + weight.longValue();
        redisTemplate.opsForZSet().add(TradeRedisKey.AUTO_INVESTORS_STASH, JSON.toJSONString(value), score);
        redisTemplate.opsForZSet().remove(zsetKey, investorId);
        log.info("投资人进入暂存区,userId={},queue={}", investorId, TradeRedisKey.AUTO_INVESTORS_STASH);
    }

    public void dequeueInvestorStash() {
        Set<ZSetOperations.TypedTuple<String>> tupleSet = redisTemplate.opsForZSet().rangeWithScores(TradeRedisKey.AUTO_INVESTORS_STASH, 0, -1);
        if (tupleSet != null && !tupleSet.isEmpty()) {
            Map<String, Set<ZSetOperations.TypedTuple<String>>> groupBySourceQueue = new HashMap<>();
            for (ZSetOperations.TypedTuple<String> tuple : tupleSet) {
                String value = tuple.getValue();
                AutoInvestorValueDTO dto = JSON.parseObject(value, AutoInvestorValueDTO.class);
                String investorId = dto.getInvestorId();
                String sourceQueue = dto.getSourceQueue();
                ZSetOperations.TypedTuple<String> newTuple = new DefaultTypedTuple<>(investorId, tuple.getScore());
                Set<ZSetOperations.TypedTuple<String>> set = groupBySourceQueue.computeIfAbsent(sourceQueue, k -> new HashSet<>());
                set.add(newTuple);
            }
            groupBySourceQueue.forEach((k, v) -> redisTemplate.opsForZSet().add(k, v));
            redisTemplate.opsForZSet().removeRange(TradeRedisKey.AUTO_INVESTORS_STASH, 0, -1);
        }
    }

    public void enqueueProjectStash(Long projectId) {
        redisTemplate.opsForZSet().add(TradeRedisKey.AUTO_INVEST_PROJECT_STASH, projectId.toString(), System.currentTimeMillis());
        log.info("标的进入暂存区,projectId={},queue={}", projectId, TradeRedisKey.AUTO_INVEST_PROJECT_STASH);
    }

    public void diffAndDiscard(String userId, List<SysInvestConfig> preSelected, List<SysInvestConfig> curSelected) {
        preSelected.stream().filter(cfg -> {
            boolean exists = false;
            for (SysInvestConfig config : curSelected) {
                if (cfg.getPeriodUnit().equals(config.getPeriodUnit())
                        && cfg.getMinDeadline().equals(config.getMinDeadline())
                        && cfg.getMaxDeadline().equals(config.getMaxDeadline())) {
                    exists = true;
                    break;
                }
            }
            return !exists;
        }).forEach(cfg -> {
            String queueName = getQueueName(cfg);
            dequeueInvestor(userId, queueName);
        });
    }
    /**
     * 删除指定用户的自动投资
     * @param userId
     */
    public void discard(String userId) {
        Set<String> keys = redisTemplate.keys(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX + "*");
        for (String key : keys) {
            redisTemplate.opsForZSet().remove(key, userId);
        }
        redisTemplate.opsForZSet().remove(TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS, userId);
        redisTemplate.opsForZSet().remove(TradeRedisKey.REST_AREA_NO_RED_ENVELOPES, userId);
        redisTemplate.opsForZSet().remove(TradeRedisKey.REST_AREA_RISK_FUND, userId);
        redisTemplate.opsForZSet().remove(TradeRedisKey.REST_AREA_UNKNOW, userId);
    }
 
    public void cleanAll() {
        logQueue();
        Set<String> keys = redisTemplate.keys(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX + "*");
        for (String key : keys) {
            redisTemplate.delete(key);
        }
        redisTemplate.delete(TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS);
        redisTemplate.delete(TradeRedisKey.REST_AREA_NO_RED_ENVELOPES);
        redisTemplate.delete(TradeRedisKey.REST_AREA_RISK_FUND);
        redisTemplate.delete(TradeRedisKey.REST_AREA_UNKNOW);
        logQueue();
    }
    /**
     * 输出队列信息
     */
    public void logQueue() {
        Set<String> keys = redisTemplate.keys(TradeRedisKey.AUTO_INVESTORS_QUEUE_PREFIX + "*");
        for (String key : keys) {
            Long count = redisTemplate.opsForZSet().count(key, Double.MIN_VALUE, Double.MAX_VALUE);
            log.info("自动投资===>队列情况,{}大小:{}", key, count);
        }
        Long notSufficientFunds = redisTemplate.opsForZSet().count(TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS, Double.MIN_VALUE, Double.MAX_VALUE);
        Long noRedEnvelope = redisTemplate.opsForZSet().count(TradeRedisKey.REST_AREA_NO_RED_ENVELOPES, Double.MIN_VALUE, Double.MAX_VALUE);
        Long riskFund = redisTemplate.opsForZSet().count(TradeRedisKey.REST_AREA_RISK_FUND, Double.MIN_VALUE, Double.MAX_VALUE);
        Long unknow = redisTemplate.opsForZSet().count(TradeRedisKey.REST_AREA_UNKNOW, Double.MIN_VALUE, Double.MAX_VALUE);
        
        log.info("自动投资===>队列情况,{}大小:{}", TradeRedisKey.REST_AREA_NOT_SUFFICIENT_FUNDS, notSufficientFunds);
        log.info("自动投资===>队列情况,{}大小:{}", TradeRedisKey.REST_AREA_NO_RED_ENVELOPES, noRedEnvelope);
        log.info("自动投资===>队列情况,{}大小:{}", TradeRedisKey.REST_AREA_RISK_FUND, riskFund);
        log.info("自动投资===>队列情况,{}大小:{}", TradeRedisKey.REST_AREA_UNKNOW, unknow);
    }

    /**
     * 被移除自动投资人队列的投资人，进入投资休息区,暂存区zset queue 的value数据模型
     * Created by zhangwanli on 2017/11/22.
     */
    @Setter
    @Getter
    public static class AutoInvestorValueDTO implements Serializable {
        private static final long serialVersionUID = -9158062359179896100L;
        private String investorId;
        private Byte weight;
        private String sourceQueue;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            AutoInvestorValueDTO that = (AutoInvestorValueDTO) o;
            return Objects.equals(investorId, that.investorId) &&
                    Objects.equals(sourceQueue, that.sourceQueue);
        }

        @Override
        public int hashCode() {
            return Objects.hash(investorId, sourceQueue);
        }

    }

}
